// 版权所有2009 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包路径实现用于操作斜杠分隔的
// 路径的实用程序。
// 
// path包应仅用于由正向
// 斜杠分隔的路径，例如URL中的路径。此软件包不处理带驱动器号或反斜杠的
// Windows路径；要操作
// 操作系统路径，请使用path/filepath包。
package path

// lazybuf是一个延迟构造的路径缓冲区。
// 支持追加、读取之前追加的字节、
// 以及检索最终字符串。它不分配一个缓冲区
// 来保持输出，直到输出与s分离。
type lazybuf struct {
	s   string
	buf []byte
	w   int
}

func (b *lazybuf) index(i int) byte {
	if b.buf != nil {
		return b.buf[i]
	}
	return b.s[i]
}

func (b *lazybuf) append(c byte) {
	if b.buf == nil {
		if b.w < len(b.s) && b.s[b.w] == c {
			b.w++
			return
		}
		b.buf = make([]byte, len(b.s))
		copy(b.buf, b.s[:b.w])
	}
	b.buf[b.w] = c
	b.w++
}

func (b *lazybuf) string() string {
	if b.buf == nil {
		return b.s[:b.w]
	}
	return string(b.buf[:b.w])
}

// Clean通过纯词法处理返回与路径
// 等效的最短路径名。它反复应用以下规则
// 直到无法进行进一步处理：
// 
// 1。将多个斜杠替换为一个斜杠。
// 2。消除每一个。路径名元素（当前目录）。
// 3。消除每一个内部缺陷。。path name元素（父目录）
// 以及非。。前面的元素。
// 4。排除以根路径开头的元素：
// 也就是说，将路径开头的“/…”替换为“/”。
// 
// 返回的路径只有在根“/”时才以斜杠结尾。
// 
// 如果此过程的结果为空字符串，则Clean 
// 返回字符串“”。
// 
// 另请参见Rob Pike，`Plan 9中的词汇文件名或
// 正确使用点，`
// https:
func Clean(path string) string {
	if path == "" {
		return "."
	}

	rooted := path[0] == '/'
	n := len(path)

	// 不变量：
	// 从路径读取；r是要处理的下一个字节的索引。
	// 写信给buf；w是要写入的下一个字节的索引。
	// dotdot是buf中的索引，其中。。必须停止，因为
	// 它是前导斜杠，或者它是前导../。。前缀
	out := lazybuf{s: path}
	r, dotdot := 0, 0
	if rooted {
		out.append('/')
		r, dotdot = 1, 1
	}

	for r < n {
		switch {
		case path[r] == '/':
			// 空路径元素
			r++
		case path[r] == '.' && (r+1 == n || path[r+1] == '/'):
			// 。元素
			r++
		case path[r] == '.' && path[r+1] == '.' && (r+2 == n || path[r+2] == '/'):
			// /。。元素：remove to last/
			r += 2
			switch {
			case out.w > dotdot:
				// 可以回溯
				out.w--
				for out.w > dotdot && out.index(out.w) != '/' {
					out.w--
				}
			case !rooted:
				// 不能回溯，但不是根元素，因此追加。。要素
				if out.w > 0 {
					out.append('/')
				}
				out.append('.')
				out.append('.')
				dotdot = out.w
			}
		default:
			// 实路径元素。
			// 根据需要添加斜杠
			if rooted && out.w != 1 || !rooted && out.w != 0 {
				out.append('/')
			}
			// 复制元素
			for ; r < n && path[r] != '/'; r++ {
				out.append(path[r])
			}
		}
	}

	// 将空字符串转换为“.”
	if out.w == 0 {
		return "."
	}

	return out.string()
}

// 最后斜杠是字符串。最后索引，“/”），但我们无法导入字符串。
func lastSlash(s string) int {
	i := len(s) - 1
	for i >= 0 && s[i] != '/' {
		i--
	}
	return i
}

// 拆分紧跟在最后一个斜杠之后的路径，
// 将其拆分为目录和文件名组件。
// 如果路径中没有斜杠，Split返回一个空目录，
// 文件设置为path。
// 返回值的属性为path=dir+file。
func Split(path string) (dir, file string) {
	i := lastSlash(path)
	return path[:i+1], path[i+1:]
}

// Join将任意数量的path元素连接到一条路径中，
// 用斜线分隔它们。空元素被忽略。
// 结果已清除。但是，如果参数列表为
// 空或其所有元素为空，Join将返回一个空字符串。
func Join(elem ...string) string {
	size := 0
	for _, e := range elem {
		size += len(e)
	}
	if size == 0 {
		return ""
	}
	buf := make([]byte, 0, size+len(elem)-1)
	for _, e := range elem {
		if len(buf) > 0 || e != "" {
			if len(buf) > 0 {
				buf = append(buf, '/')
			}
			buf = append(buf, e...)
		}
	}
	return Clean(string(buf))
}

// Ext返回path使用的文件扩展名。
// 扩展名是path的最后一个斜杠分隔元素中从最后一个点开始的后缀
// ；
// 如果没有点，则为空。
func Ext(path string) string {
	for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
		if path[i] == '.' {
			return path[i:]
		}
	}
	return ""
}

// Base返回path的最后一个元素。
// 在提取最后一个元素之前，删除后面的斜杠。
// 如果路径为空，则Base返回“”。
// 如果路径完全由斜杠组成，则Base返回“/”。
func Base(path string) string {
	if path == "" {
		return "."
	}
	// 带尾部斜杠。
	for len(path) > 0 && path[len(path)-1] == '/' {
		path = path[0 : len(path)-1]
	}
	// 查找最后一个元素
	if i := lastSlash(path); i >= 0 {
		path = path[i+1:]
	}
	// 如果现在为空，则它只有斜杠。
	if path == "" {
		return "/"
	}
	return path
}

// isbs报告路径是否为绝对路径。
func IsAbs(path string) bool {
	return len(path) > 0 && path[0] == '/'
}

// Dir返回path的最后一个元素以外的所有元素，通常是path的目录。
// 使用Split删除最后一个元素后，将清除路径并删除尾部的
// 斜杠。
// 如果路径为空，则Dir返回“”。
// 如果路径完全由斜杠和非斜杠字节组成，则Dir 
// 返回一个斜杠。在任何其他情况下，返回的路径都不会以
// 斜杠结尾。
func Dir(path string) string {
	dir, _ := Split(path)
	return Clean(dir)
}
